/*
 * Copyright (C) 2025 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package top.xbaistack.config;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.Formatter;

import java.io.IOException;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

/**
 * JSON 预配置
 *
 * @author xbaistack
 * @since 2025/03/25
 */
@Configuration
@ConditionalOnClass(ObjectMapper.class)
@AutoConfigureBefore(JacksonAutoConfiguration.class)
public class JacksonConfiguration {

    private static final String HH_MM_SS = "HH:mm:ss";
    private static final String YYYY_MM_DD = "yyyy-MM-dd";
    private static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";

    @Bean
    public Module simpleModule() {
        SimpleModule module = new SimpleModule();
        module.addSerializer(long.class, new Long2StringSerializer()); // Long -> "Long"
        module.addSerializer(Long.class, new Long2StringSerializer()); // Long -> "Long"
        return module;
    }

    @Bean
    public JavaTimeModule javaTimeModule() {
        DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern(HH_MM_SS);
        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(YYYY_MM_DD);
        DateTimeFormatter datetimeFormatter = DateTimeFormatter.ofPattern(YYYY_MM_DD_HH_MM_SS);

        JavaTimeModule module = new JavaTimeModule();
        // Serializer
        module.addSerializer(LocalTime.class, new LocalTimeSerializer(timeFormatter));
        module.addSerializer(LocalDate.class, new LocalDateSerializer(dateFormatter));
        module.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(datetimeFormatter));
        // Deserializer
        module.addDeserializer(LocalTime.class, new LocalTimeDeserializer(timeFormatter));
        module.addDeserializer(LocalDate.class, new LocalDateDeserializer(dateFormatter));
        module.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(datetimeFormatter));
        return module;
    }

    @Bean
    public Formatter<LocalDateTime> localDateTimeFormatter() {
        return new Formatter<LocalDateTime>() {
            @Override
            public LocalDateTime parse(String text, Locale locale) {
                if (StringUtils.isEmpty(text)) {
                    return null;
                }
                int length = text.length();
                if (length == HH_MM_SS.length()) {
                    return LocalDateTime.parse(text, DateTimeFormatter.ofPattern(HH_MM_SS));
                }
                if (length == YYYY_MM_DD.length()) {
                    return LocalDateTime.parse(text, DateTimeFormatter.ofPattern(YYYY_MM_DD));
                }
                if (length == YYYY_MM_DD_HH_MM_SS.length()) {
                    return LocalDateTime.parse(text, DateTimeFormatter.ofPattern(YYYY_MM_DD_HH_MM_SS));
                }
                if (StringUtils.isNumeric(text)) {
                    return Instant.ofEpochMilli(Long.parseLong(text)).atZone(ZoneOffset.ofHours(8))
                            .toLocalDateTime();
                }
                return null;
            }

            @Override
            public String print(LocalDateTime object, Locale locale) {
                return DateTimeFormatter.ofPattern(YYYY_MM_DD_HH_MM_SS, locale).format(object);
            }
        };
    }

    static class Long2StringSerializer extends JsonSerializer<Long> {

        /** The minimum value of javascript support: -2<sup>53</sup>( {@value} ) */
        public static final long MIN_VALUE = 0xffe0000000000000L;

        /** The maximum value of javascript support: 2<sup>53</sup>( {@value} ) */
        public static final long MAX_VALUE = 0x20000000000000L;

        @Override
        public void serialize(Long value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            if (value == null) {
                gen.writeNull();
            } else if (value >= MAX_VALUE || value <= MIN_VALUE) {
                gen.writeString(String.valueOf(ObjectUtils.defaultIfNull(value, 0L)));
            } else {
                gen.writeNumber(value.toString());
            }
        }

    }

}
